﻿#region

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using Tateeda.ClinicalStudy.Base.Data.DBModel;
using Tateeda.ClinicalStudy.Base.Data.EF;
using Tateeda.ClinicalStudy.Base.Modules.Common.Enumeration;
using Tateeda.ClinicalStudy.Base.Modules.Common.Extenstions;
using Tateeda.ClinicalStudy.Base.Modules.Infrastructure.Interfaces.Services;
using Tateeda.ClinicalStudy.Base.Modules.Models.Study;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyForm;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyVisit;

#endregion

#region - Legal -

///<legal>
/// Copyright © 2011, Tateeda Media Network
/// All rights reserved. http://tateeda.com
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions
/// are met:
///
/// - Redistributions of source code must retain the above copyright
/// notice, this list of conditions and the following disclaimer.
///
/// - Neither the name of the Tateeda Media Network, nor the names of its
/// contributors may be used to endorse or promote products
/// derived from this software without specific prior written
/// permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
/// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
/// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
/// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
/// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
/// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
/// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
/// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///</legal>

#endregion - Legal -

namespace Tateeda.ClinicalStudy.Base.Modules.Infrastructure.Concrete.Services {

    public class AppointmentService : IAppointmentService {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IUserService _userService = new UserService();

        public AppointmentService() {
            _unitOfWork = new SqlUnitOfWork();
        }

        #region IAppointmentService Members

        public virtual IUnitOfWork UnitOfWork {
            get { return _unitOfWork; }
        }

        public virtual ICollection<VisitModel> GetVisits(StudyStatus status) {
            return
                UnitOfWork.Visits.Where(v => v.Status == (int)status).OrderBy(v => v.SortOrder).ToList().
                    ToVisitModelList();
        }

        public virtual ICollection<VisitModel> GetVisitsForForm(int formId) {
            IQueryable<Visit> visits = from vf in UnitOfWork.VisitForms
                                       where vf.FormId == formId
                                       orderby vf.SortOrder
                                       select vf.Visit;
            return visits.ToList().ToVisitModelList();
        }

        public virtual VisitModel GetVisit(int visitId) {
            return UnitOfWork.Visits.Where(v => v.VisitId == visitId).FirstOrDefault().ToVisitModel();
        }

        public virtual ICollection<StudyFormModel> GetFormsForVisit(int visitId, StudyFormType formType) {
            var query = from f in UnitOfWork.Forms
                        join vf in UnitOfWork.VisitForms
                            on f.FormId equals vf.FormId
                        where vf.VisitId == visitId && f.FormType == (int)formType
                        orderby vf.SortOrder, f.Name
                        select f;

            var allVisitForms = UnitOfWork.VisitForms.ToList();
            var list = new Collection<StudyFormModel>();
            foreach (Form f in query) {
                f.SortOrder = allVisitForms.Find(vf => vf.FormId == f.FormId).SortOrder;
                list.Add(f.ToFormModel());
            }
            return list;
        }

        public virtual ICollection<AppointmentModel> FindAppointmentsForDates(DateTime from, DateTime to, int siteId,
                                                                       int appUserId, bool showForAll = false) {
            var utcFrom = from.ToUniversalTime();
            var utcTo = to.ToUniversalTime();

            var qery = UnitOfWork.Appointments
                .Where(a => a.SiteId == siteId
                            && a.StartTime >= utcFrom
                            && a.StartTime < utcTo).AsQueryable();

            if (!showForAll) {
                qery = qery.Where(a => a.AppUserId == appUserId);
            }
            qery = qery.OrderBy(a => a.SubjectId); //Second param for order by in SQL
            qery = qery.OrderBy(a => a.StartTime); //First param for order by in SQL
            return ConvertAppointments(qery.ToList());
        }

        public virtual AppointmentModel GetAppointment(int appointmentId) {
            var query = UnitOfWork.Appointments.Include("AppUser")
                .Include("Visit")
                .Include("Subject")
                .Include("RecreationalDrugOrSubstances")
                .Where(a => a.AppointmentId == appointmentId).SingleOrDefault();

            return query.ToAppointmentModel();
        }

        public virtual ICollection<StudyFormModel> GetAppointmentFormsAll(int appointmentId) {
            var query = from ap in UnitOfWork.Appointments
                        join v in UnitOfWork.Visits
                            on ap.VisitId equals v.VisitId
                        join vf in UnitOfWork.VisitForms
                            on v.VisitId equals vf.VisitId
                        join f in UnitOfWork.Forms
                            on vf.FormId equals f.FormId
                        where ap.AppointmentId == appointmentId
                        orderby v.Name, f.SortOrder
                        select f;

            var forms = query.ToList().ToFormModelList();
            return forms;
        }

        /// <summary>
        ///   Appointment Form has only status for in progress or completed form (may be other)
        ///   BUT It will never have NOT STARTED form. to If we are looking for not started status we just need to get it
        ///   from VisitForms and subtract any forms in AppointmentForm table
        ///   Gets the appointment forms for status.
        /// </summary>
        /// <param name = "appointmentId">The appointment id.</param>
        /// <param name = "statusId">The status id.</param>
        /// <returns></returns>
        public virtual ICollection<StudyFormModel> GetAppointmentFormsForStatus(int appointmentId, int statusId) {
            var query = from af in UnitOfWork.AppointmentForms
                        join f in UnitOfWork.Forms
                            on af.FormId equals f.FormId
                        where af.AppointmentId == appointmentId && af.Status == statusId
                        orderby f.SortOrder
                        select f;

            var forms = query.ToList().ToFormModelList();
            return forms;
        }

        #endregion

        #region - Private functions -

        /// <summary>
        ///   Converts the appointments.
        /// </summary>
        /// <param name = "appointments">The appointments.</param>
        /// <returns></returns>
        private ICollection<AppointmentModel> ConvertAppointments(IEnumerable<Appointment> appointments) {
            var result = new Collection<AppointmentModel>();
            if (appointments != null) {
                foreach (var appt in appointments)
                    result.Add(ConvertFromUniversalTime(appt.ToAppointmentModel()));
            }
            return result;
        }

        private AppointmentModel ConvertFromUniversalTime(AppointmentModel appt) {
            appt.StartTime = ConvertTimeFromUTC(appt.AppUserId, appt.StartTime);
            appt.EndTime = ConvertTimeFromUTC(appt.AppUserId, appt.EndTime);
            return appt;
        }

        /// <summary>
        ///   Converts the time from UTC.
        /// </summary>
        /// <param name = "userId"></param>
        /// <param name = "universalTime">The universal time.</param>
        /// <returns></returns>
        private DateTime ConvertTimeFromUTC(int userId, DateTime universalTime) {
            TimeZoneInfo destTimeZone = _userService.GetUserTimeZoneInfo(userId);
            DateTime time = DateTime.SpecifyKind(universalTime, DateTimeKind.Utc);
            DateTime destTime = TimeZoneInfo.ConvertTimeFromUtc(time, destTimeZone);
            return destTime;
        }

        #endregion - Private functions -
    }
}